/*
 *  Created at 2012-04-09 20:02 GMT-03:00
 *  Author: Victor Williams Stafusa da Silva
 *  JSmartUnit
 */
package org.jsmartunit.model;

import java.lang.reflect.InvocationTargetException;
import org.junit.runners.model.InitializationError;

/**
 * @author Victor Williams Stafusa da Silva
 */
public interface InstanceFactory<E> {

    public Class<? extends E> getTargetClass();

    public E getInstance() throws InitializationError;

    public static final class Factories {
        private Factories() {}

        public static <E> InstanceFactory<E> box(E instance) {
            return new Box<E>(instance);
        }

        public static <E> InstanceFactory<E> cache(InstanceFactory<E> decorated) {
            return new Cache<E>(decorated);
        }

        public static <E> InstanceFactory<E> creator(Class<E> instanceClass) {
            return new Creator<E>(instanceClass);
        }

        private static class Box<E> implements InstanceFactory<E> {
            private final E instance;

            public Box(E instance) {
                this.instance = instance;
            }

            @Override
            @SuppressWarnings("unchecked")
            public Class<? extends E> getTargetClass() {
                return (Class<? extends E>) instance.getClass();
            }

            @Override
            public E getInstance() {
                return instance;
            }
        }

        private static class Cache<E> implements InstanceFactory<E> {
            private final InstanceFactory<E> decorated;
            private E instance;

            public Cache(InstanceFactory<E> decorated) {
                this.decorated = decorated;
                this.instance = null;
            }

            @Override
            public Class<? extends E> getTargetClass() {
                return decorated.getTargetClass();
            }

            @Override
            public E getInstance() throws InitializationError {
                if (instance == null) instance = decorated.getInstance();
                return instance;
            }
        }

        private static class Creator<E> implements InstanceFactory<E> {
            private final Class<E> instanceClass;

            public Creator(Class<E> instanceClass) {
                this.instanceClass = instanceClass;
            }

            @Override
            public Class<? extends E> getTargetClass() {
                return instanceClass;
            }

            @Override
            public E getInstance() throws InitializationError {
                try {
                    return instanceClass.getConstructor().newInstance();
                } catch (InvocationTargetException e) {
                    throw new InitializationError(e.getCause());
                } catch (Throwable e) {
                    throw new InitializationError(e);
                }
            }
        }
    }
}
